package watcher

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

type Exporter func(map[string]string) (string, error)

var cores float64

func (pws *ProcWatcherService) Refresh() error {
	cmdstr := `grep -c processor /proc/cpuinfo`
	cmd := exec.Command(`/bin/sh`, `-c`, cmdstr)
	out, _ := cmd.CombinedOutput()
	cmdout := string(out)
	cores, _ = strconv.ParseFloat(strings.TrimSpace(cmdout), 64)
	tick := time.Tick(time.Millisecond * time.Duration(pws.RefreshInterval))
	for {
		select {
		case <-tick:
			err := pws.getMetric()
			checkErr(err)
		case <-pws.stopping:
			pws.mu.Lock()
			pws.mu.Unlock()
			close(pws.stopped)
		}
	}
}

func (pws *ProcWatcherService) Close() {
	close(pws.stopping)
	<-pws.stopped
}

func (pws *ProcWatcherService) getMetric() error {
	pws.mu.Lock()
	defer pws.mu.Unlock()
	if err := pws.scan(); err != nil {
		return err
	}
	if pws.value == nil {
		pws.value = make(map[string]*pwMetric)
	}
	for pname, v := range pws.procs {
		if _, ok := pws.value[pname]; !ok {
			pws.value[pname] = &pwMetric{}
		}
		if _, ye := v.Tags["port"]; !ye {
			v.Tags["port"] = ""
		}
		stat, err := v.process.Status(v.Tags["port"])
		if err != nil {
			return err
		}
		if IsNull(stat) {
			pws.value[pname].stat = nil
			pws.value[pname].duration = 0
			continue
		}
		pws.value[pname].stat = stat
		pws.value[pname].duration = v.process.Uptime()
	}
	pws.lastScanTime = time.Now().UnixNano() / FT
	return nil
}

func (pws *ProcWatcherService) Export(tags map[string]string) (string, error) {
	ret := make(map[string][]string)
	_, ok := tags["host"]
	if !ok {
		cmd := exec.Command("/bin/hostname", "-f")
		var out bytes.Buffer
		cmd.Stdout = &out
		err := cmd.Run()
		if err != nil {
			log.Error(`run "hostname -f":`, err)
			tags["host"], _ = os.Hostname()
		} else {
			fqdn := out.String()
			fqdn = fqdn[:len(fqdn)-1]
			tags["host"] = fqdn
		}
	}
	var buf bytes.Buffer
	for k, v := range tags {
		buf.WriteString(fmt.Sprintf(`%s="%s",`, k, v))
	}
	str := buf.String()
	pws.mu.RLock()
	defer pws.mu.RUnlock()
	if pws.value == nil {
		return "N/a\n", nil
	}
	interval := float64(pws.RefreshInterval) / 1000
	for pname, v := range pws.procs {
		buf.Reset()
		for key, val := range v.Tags {
			if val == "" {
				continue
			}
			buf.WriteString(fmt.Sprintf(`%s="%s",`, key, val))
		}
		proc_msg := buf.String()
		name := fmt.Sprintf(`%s%sproc_name="%s"`, str, proc_msg, pname)
		format := "%s{%s} %d %d"
		format1 := `%s{%s,m="%s",u="%s"} %.2f %d`
		format3 := `%s{%s,m="%s",u="%s"} %d %d`
		format2 := `%s{%s,m="%s"} %d %d`
		if IsNull(pws.value[pname].stat) {
			ret["proc_stat"] = append(ret["proc_stat"],
				fmt.Sprintf(format2, "proc_stat", name, "isRunning", 0, pws.lastScanTime))
			continue
		}
		duration := pws.value[pname].duration
		stat := pws.value[pname].stat
		ret["proc_stat"] = append(ret["proc_stat"],
			fmt.Sprintf(format2, "proc_stat", name, "isRunning", 1, pws.lastScanTime))
		ret["proc_uptime"] = append(ret["proc_uptime"],
			fmt.Sprintf(`%s{%s,u="%s"} %d %d`, "proc_uptime", name, "ms", duration, pws.lastScanTime))

		ret["proc_cpu"] = append(ret["proc_cpu"],
			fmt.Sprintf(format3, "proc_cpu", name, "proc_cutime", "ms", stat.CuTime, pws.lastScanTime))

		ret["proc_cpu"] = append(ret["proc_cpu"],
			fmt.Sprintf(format3, "proc_cpu", name, "proc_cstime", "ms", stat.CsTime, pws.lastScanTime))

		ret["proc_cpu"] = append(ret["proc_cpu"],
			fmt.Sprintf(format3, "proc_cpu", name, "proc_stime", "ms", stat.STime, pws.lastScanTime))

		ret["proc_cpu"] = append(ret["proc_cpu"],
			fmt.Sprintf(format3, "proc_cpu", name, "proc_utime", "ms", stat.UTime, pws.lastScanTime))

		ret["proc_cpu"] = append(ret["proc_cpu"],
			fmt.Sprintf(`%s{%s,m="%s",u="%s"} %.2f %d`, "proc_cpu", name, "cpu_usage", "%", stat.CpuUsage, pws.lastScanTime))

		//		ret["proc_blkio_ticks"] = append(ret["proc_blkio_ticks"],
		//			fmt.Sprintf(format, "proc_blkio_ticks", name, stat.BlkioTicks, pws.lastScanTime))

		ret["proc_memory"] = append(ret["memory"],
			fmt.Sprintf(format1, "proc_memory_size", name, "memory_size", "kB", float64(stat.MemSize)/1024, pws.lastScanTime))

		ret["proc_thread_count"] = append(ret["proc_thread_count"],
			fmt.Sprintf(format2, "proc_thread_count", name, "proc_thread_count", stat.ThreadCount, pws.lastScanTime))

		ret["proc_count"] = append(ret["proc_count"],
			fmt.Sprintf(format2, "proc_count", name, "proc_count", stat.ProcCount, pws.lastScanTime))

		ret["proc_diskIO"] = append(ret["proc_diskIO"],
			fmt.Sprintf(format1, "proc_diskIO", name, "read_bytes", "kB/s", float64(stat.ReadBytes)/1024/interval, pws.lastScanTime))

		ret["proc_diskIO"] = append(ret["proc_diskIO"],
			fmt.Sprintf(format1, "proc_diskIO", name, "write_bytes", "kB/s", float64(stat.WriteBytes)/1024/interval, pws.lastScanTime))

		ret["proc_diskIO"] = append(ret["proc_diskIO"],
			fmt.Sprintf(format1, "proc_diskIO", name, "read_count", "r/s", float64(stat.ReadCount)/interval, pws.lastScanTime))

		ret["proc_diskIO"] = append(ret["proc_diskIO"],
			fmt.Sprintf(format1, "proc_diskIO", name, "write_count", "w/s", float64(stat.WriteCount)/interval, pws.lastScanTime))

		ret["proc_netIO"] = append(ret["proc_netIO"],
			fmt.Sprintf(format1, "proc_netIO", name, "bytes_sent", "kB/s", float64(stat.BytesSent)/1024/interval, pws.lastScanTime))

		ret["proc_netIO"] = append(ret["proc_netIO"],
			fmt.Sprintf(format1, "proc_netIO", name, "bytes_recv", "kB/s", float64(stat.BytesRecv)/1024/interval, pws.lastScanTime))

		ret["proc_netIO"] = append(ret["proc_netIO"],
			fmt.Sprintf(format1, "proc_netIO", name, "packets_sent", "packet/s", float64(stat.PacketsSent)/interval, pws.lastScanTime))

		ret["proc_netIO"] = append(ret["proc_netIO"],
			fmt.Sprintf(format1, "proc_netIO", name, "packets_recv", "packet/s", float64(stat.PacketsRecv)/interval, pws.lastScanTime))

		var s []string
		for k, v := range stat.TcpCount {
			s = append(s, fmt.Sprintf(format, "proc_tcp_count", fmt.Sprintf(`%s, status="%s"`, name, k), v, pws.lastScanTime))
		}
		if len(s) > 0 {
			ret["proc_tcp_count"] = append(ret["proc_tcp_count"], strings.Join(s, "\n"))
		}
	}
	buf.Reset()
	for k, v := range ret {
		s := fmt.Sprintf("# TYPE %s gauge\n", k)
		if k == "proc_uptime" {
			s = fmt.Sprintf("# TYPE %s counter\n", k)
		}
		buf.WriteString(s)
		for _, l := range v {
			buf.WriteString(l)
			buf.WriteString("\n")
		}
	}
	return buf.String(), nil
}
